#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#include "zepto.h"

static int str_getc(void *stream);
//static char *str_gets(void *stream);
static int str_ungetc(void *stream, int c);
//static int str_putc(void *stream, int c);
static int str_puts(void *stream, const char *str);
static int str_close(void *stream);
static int str_eof(void *stream);

// TODO: add putc and gets
stream_ops_t string_ops = {
  .sgetc = str_getc,
  .sgets = NULL,
  .sungetc = str_ungetc,
  .sputc = NULL,
  .sputs = str_puts,
  .sclose = str_close,
  .seof = str_eof,
};

static int file_getc(void *stream);
//static char *file_gets(void *stream);
static int file_ungetc(void *stream, int c);
//static int file_putc(void *stream, int c);
static int file_puts(void *stream, const char *str);
static int file_close(void *stream);
static int file_eof(void *stream);

// TODO: add putc and gets
stream_ops_t file_ops = {
  .sgetc = file_getc,
  .sgets = NULL,
  .sungetc = file_ungetc,
  .sputc = NULL,
  .sputs = file_puts,
  .sclose = file_close,
  .seof = file_eof,
};

typedef struct __str_stream {
  char *string;
  int length;
  int pos;
} str_stream_t;

typedef FILE* file_stream_t;

stream_t *_stream_open_file(const char *path, stream_mode_t mode)
{
  stream_t *stream;
  FILE *file;

  file = fopen(path, mode == STREAM_IN ? "r" : "w");
  if(!file) return NULL;
  
  stream = zepto_alloc_stream();
  stream->data = (void *)file;
  stream->ops = &file_ops;
  
  return stream;
}

stream_t *_stream_wrap_stdio(FILE *file, stream_mode_t mode)
{
  stream_t *stream;

  stream = zepto_alloc_stream();
  stream->ops = &file_ops;
  stream->data = file;

  return stream;
}

stream_t *_stream_open_string(char *string, stream_mode_t mode)
{
  stream_t *stream;
  str_stream_t *str_stream;

  ASSERT(mode == STREAM_IN);

  str_stream = zepto_alloc_general(sizeof(*str_stream));
  str_stream->string = strdup(string);
  
  ASSERT(str_stream->string != NULL);
  
  str_stream->length = strlen(string);
  str_stream->pos = 0;

  stream = zepto_alloc_stream();
  stream->ops = &string_ops;
  stream->data = str_stream;

  return stream;
}

/*struct zepto *zepto_open_file(struct zepto *path, struct zepto *mode)
{
  struct zepto *expr;
  stream_t *stream;

  CHECK_TYPE(path, ZEPTO_STRING);
  CHECK_TYPE(mode, ZEPTO_INTEGER);

  stream = _stream_open_file(path->data.string, mode->data.integer);
  if(!stream) {
    // #f means fail
    return FALSE;
  }
  expr = zepto_alloc();
  
  expr->type = ZEPTO_STREAM;
  expr->data.stream = stream;

  return expr;
}

struct zepto *zepto_open_string(struct zepto *string, struct zepto *mode)
{
  struct zepto *expr;
  stream_t *stream;

  CHECK_TYPE(string, ZEPTO_STRING);
  CHECK_TYPE(mode, ZEPTO_INTEGER);
  
  stream = _stream_open_string(string->data.string, mode->data.integer);
  if(!stream) {
    // #f means fail
    return FALSE;
  }
  expr = zepto_alloc();
  
  expr->type = ZEPTO_STREAM;
  expr->data.stream = stream;

  return expr;
  }*/
/*
struct zepto *zepto_getc(struct zepto *expr)
{
  struct zepto *res;
  int c;
  CHECK_TYPE(expr, ZEPTO_STREAM);
  
  c = _stream_getc(expr->data.stream);
  if(c == EOF) {
    res = FALSE;
  } else {
    res = zepto_alloc();
    res->type = ZEPTO_CHAR;
    res->data.character = (char)c;
  }
  return res;
}

struct zepto *zepto_putc(struct zepto *stream, struct zepto *ch)
{
  struct zepto *res;
  CHECK_TYPE(stream, ZEPTO_STREAM);
  CHECK_TYPE(ch, ZEPTO_CHAR);
  ZEPTO_NEG_RESULT(
    res, _stream_putc(stream->data.stream, ch->data.character), EOF);
  return res;
}

struct zepto *zepto_puts(struct zepto *stream, struct zepto *str)
{
  struct zepto *res;
  CHECK_TYPE(stream, ZEPTO_STREAM);
  CHECK_TYPE(str, ZEPTO_STRING);
  ZEPTO_NEG_RESULT(
    res, _stream_puts(stream->data.stream, str->data.string), EOF);
  return res;
}

struct zepto *zepto_close(struct zepto *stream)
{
  CHECK_TYPE(stream, ZEPTO_STREAM);
  _stream_close(stream->data.stream);
  return TRUE;
  }*/

// Different streams implementation:

#define STREAM ((str_stream_t *)stream)

static int str_getc(void *stream)
{
  int ret;
  if(STREAM->pos < STREAM->length) {
    ret = STREAM->string[STREAM->pos];
    STREAM->pos++;
  } else {
    ret = -1;
  }
  return 0;
}

static int str_ungetc(void *stream, int c)
{
  if(STREAM->pos > 0) {
    STREAM->pos--;
    STREAM->string[STREAM->pos] = (char)c;
  }
  return (int)c;
}

// TODO:
static int str_puts(void *stream, const char *str)
{
  return -1;
}

// TODO:
static int str_close(void *stream)
{
  return -1;
}

static int str_eof(void *stream)
{
  return (STREAM->pos >= STREAM->length);
}

#undef STREAM

#define STREAM ((FILE *)stream)

static int file_getc(void *stream)
{
  return fgetc(STREAM);
}

static int file_ungetc(void *stream, int c)
{
  return ungetc(c, STREAM);
}

static int file_puts(void *stream, const char *str)
{
  return fputs(str, STREAM);
}

static int file_close(void *stream)
{
  return fclose(STREAM);
}

static int file_eof(void *stream)
{
  return feof(STREAM);
}

